﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Xml;
using VzaarAPI.Transport;

namespace VzaarAPI
{
	/// <summary>
	/// 
	/// </summary>
	public enum VideoProfile
	{
		Small = 1,
		Medium = 2,
		Large = 3,
		HighDefinition = 4,
		Original = 5,
		Custom = 6
	}

	/// <summary>
	/// Vzaar API access. This is the main class for accessing vzaar through
	/// their RESTful API.
	///
	/// Some of the methods require authentication which is done using OAuth.
	/// If you need to use these methods then ensure you create a Vzaar
	/// object with your OAuth credentials. The OAuth token is typically
	/// your username and the OAuth secret can be retrieved from your 
	/// account page on <a target="_blank" href="http://vzaar.com">vzaar.com</a>.    
	/// </summary>
	public class Vzaar
	{
		#region Public Constants

		/// <summary>
		/// The live vzaar API URL ("https://vzaar.com/"). This is the default URL.
		/// </summary>
		public const string URL_LIVE = "https://vzaar.com/";

		#endregion

		#region Private Members

		private bool _trustedOnly = true;
		private IVzaarTransport _transport;

		#endregion

		#region Public Methods

		/// <summary>
		/// A no parameter constructor. Uses the default LIVE Url https://vzaar.com/
		/// </summary>
		public Vzaar()
		{
		}


		/// <summary>
		/// A constructor that takes the oAuth Token and oAuth Secret as parameters.
		/// By default the Vzaar API Url will be https://sandbox.vzaar.com/.
		/// secret and token are only used for the 2-party oAuth implementation.
		/// </summary>
		/// <param name="vzaarApiToken">vzaar API token</param>
		/// <param name="vzaarUsername">vzaar Username</param>
		public Vzaar(string vzaarApiToken, string vzaarUsername)
			: this(URL_LIVE, vzaarApiToken, vzaarUsername)
		{
			url = URL_LIVE;
			token = vzaarApiToken;
			secret = secret;
		}

		/// <summary>
		/// A constructor that takes the oAuth Token, oAuth Secret and Vzaar API Url.
		/// </summary>
		/// <param name="url">The Vzaar API Url</param>
		/// <param name="vzaarApiToken">The oAuth Token (Generated by Vzaar at http://vzaar.com/settings/api)</param>
		/// <param name="vzaarUsername">The oAuth Secret (Vzaar username)</param>
		public Vzaar(string url, string vzaarApiToken, string vzaarUsername)
		{
			token = vzaarApiToken;
			secret = vzaarUsername;
			this.url = url;

			_transport = VzaarTransportFactory.CreateDefaultTransport();
			_transport.SetUrl(url);
			_transport.SetOAuthTokens(vzaarUsername, vzaarApiToken);
		}

		/// <summary>
		/// Get/Set the API URL being used.
		/// </summary>
		public string url
		{
			get;
			set;
		}

		public string token
		{
			get;
			set;
		}

		public string secret
		{
			get;
			set;
		}


		/// <summary>
		/// Is the upload method accepting only trusted formats. If this is set
		/// to true then any formats that are not on the trusted list will throw
		/// an exception up front on the uploadVideo() method. Otherwise all
		/// accepted formats will be allowed.
		/// </summary>
		/// <param name="trustedOnly">true if only trusted formats should be accepted,
		///	false if untrusted as well as trusted should be accepted.</param>
		public void AcceptTrustedFormatsOnly(bool trustedOnly)
		{
			_trustedOnly = trustedOnly;
		}

		/// <summary>
		/// Is the upload method accepting only trusted formats. If this is set
		/// to true then any formats that are not on the trusted list will throw
		/// an exception up front on the uploadVideo() method. Otherwise all
		/// accepted formats will be allowed.
		/// </summary>
		/// <returns>True if only trusted formats are being accepted</returns>
		public bool IsAcceptingTrustedFormatsOnly()
		{
			return _trustedOnly;
		}


		/// <summary>
		/// Get a user's public details along with it's relevant metadata.
		/// The user name must be used and not the email address.
		/// </summary>
		/// <param name="username">The vzaar login for that user.</param>
		/// <returns>The user information</returns>
		public User GetUserDetails(string username)
		{
			var uri = username + ".xml";

			var response = _transport.SendGetRequest(uri, null);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return new User(
				XmlHelper.GetDouble(document, "version"),
				XmlHelper.GetValue(document, "author_name"),
				XmlHelper.GetInteger(document, "author_id"),
				XmlHelper.GetValue(document, "author_url"),
				XmlHelper.GetInteger(document, "author_account"),
				XmlHelper.GetValue(document, "created_at"),
				XmlHelper.GetInteger(document, "video_count"),
				XmlHelper.GetInteger(document, "play_count"));
		}

		/// <summary>
		/// Get the details and rights for an account type.
		/// </summary>
		/// <param name="accountType">The account type to fetch the details for.</param>
		/// <returns>Account type details object</returns>
		public AccountType GetAccountType(int accountType)
		{
			var uri = "accounts/" + accountType + ".xml";

			var response = _transport.SendGetRequest(uri, null);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return new AccountType(
				XmlHelper.GetDouble(document, "version"),
				XmlHelper.GetInteger(document, "account_id"),
				XmlHelper.GetValue(document, "title"),
				XmlHelper.GetInteger(document, "monthly"),
				XmlHelper.GetValue(document, "currency"),
				XmlHelper.GetLong(document, "bandwidth"),
				XmlHelper.GetBoolean(document, "borderless"),
				XmlHelper.GetBoolean(document, "searchEnhancer"));
		}


		/// <summary>
		/// Get a list of the user's active videos along with it's relevant 
		/// metadata.
		/// </summary>
		/// <param name="request">The video list request parameters.</param>
		/// <returns>A list of videos for the request object </returns>
		public List<Video> GetVideoList(VideoListRequest request)
		{
			var username = request.username;

			if (username == null)
			{
				throw new VzaarException(
					"Required parameter 'username' is missing from request");
			}

			var uri = username + "/videos.xml";

			var response = _transport.SendGetRequest(uri, request.GetParameters());
			
			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			var iterator = XmlHelper.GetNodes(document, "video");

			var count = request.count;
			count = (count <= 0) ? 20 : count;

			if (count > iterator.Count)
			{
				count = iterator.Count;
			}

			var videoList = new List<Video>(count);
			videoList.AddRange(from XmlNode current in iterator
							   select new Video(XmlHelper.GetDouble(current, "version"),
								   XmlHelper.GetInteger(current, "id"),
								   XmlHelper.GetValue(current, "title"),
								   XmlHelper.GetValue(current, "description"),
								   XmlHelper.GetValue(current, "created_at"),
								   XmlHelper.GetValue(current, "url"),
								   XmlHelper.GetValue(current, "thumbnail_url"),
								   XmlHelper.GetInteger(current, "play_count"),
								   XmlHelper.GetValue(current, "author_name"),
								   XmlHelper.GetValue(current, "author_url"),
								   XmlHelper.GetInteger(current, "author_account"),
								   XmlHelper.GetInteger(current, "video_count"),
								   XmlHelper.GetDouble(current, "duration"),
								   XmlHelper.GetInteger(current, "width"),
								   XmlHelper.GetInteger(current, "height")));

			return videoList;
		}


		/// <summary>
		/// Get a video details including embed code.
		/// </summary>
		/// <param name="request">The video detail request parameters</param>
		/// <returns>Video details object</returns>
		public VideoDetails GetVideoDetails(VideoDetailsRequest request)
		{
			if (request == null)
			{
				throw new VzaarException("The VideoDetailsRequest object is null.");
			}

			var videoId = request.GetVideoId();
			if (videoId <= 0)
			{
				throw new VzaarException("Required video id is missing from request.");
			}

			var uri = "videos/" + videoId + ".xml";

			var response = _transport.SendGetRequest(uri, request.GetParameters());

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());

			var details = new VideoDetails
			{
				version = XmlHelper.GetDouble(document, "version"),
				type = XmlHelper.GetValue(document, "type"),
				title = XmlHelper.GetValue(document, "title"),
				description = XmlHelper.GetValue(document, "description"),
				playCount = XmlHelper.GetInteger(document, "play_count"),
				authorName = XmlHelper.GetValue(document, "author_name"),
				authorUrl = XmlHelper.GetValue(document, "author_url"),
				authorAccount = XmlHelper.GetInteger(document, "author_account"),
				providerName = XmlHelper.GetValue(document, "provider_name"),
				providerUrl = XmlHelper.GetValue(document, "provider_url"),
				thumbnailUrl = XmlHelper.GetValue(document, "thumbnail_url"),
				thumbnailWidth = XmlHelper.GetInteger(document, "thumbnail_width"),
				thumbnailHeight = XmlHelper.GetInteger(document, "thumbnail_height"),
				framegrabUrl = XmlHelper.GetValue(document, "framegrab_url"),
				framegrabWidth = XmlHelper.GetInteger(document, "framegrab_width"),
				framegrabHeight = XmlHelper.GetInteger(document, "framegrab_height"),
				html = XmlHelper.GetValue(document, "html"),
				width = XmlHelper.GetInteger(document, "width"),
				height = XmlHelper.GetInteger(document, "height"),
				borderless = XmlHelper.GetBoolean(document, "borderless"),
				duration = XmlHelper.GetDouble(document, "duration"),
				videoStatus = XmlHelper.GetInteger(document, "video_status_id"),
				videoStatusDescription = XmlHelper.GetValue(document, "video_status_description")
			};
			return details;
		}


		/// <summary>
		/// Test method for authentication.
		/// </summary>
		/// <returns>The user name of the authenticated user</returns>
		public string WhoAmI()
		{
			const string uri = "test/whoami";

			var response = _transport.SendGetRequest(uri, null);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return XmlHelper.GetValue(document, "login");
		}

		/**
		 * Upload file to Amazon AWS bucket
		 */
		public string Upload(string file)
		{
			try
			{
				if (_trustedOnly)
				{
					if (!AcceptedFileExtension.IsTrustedFormat(file))
					{
						throw new VzaarException(
							"Video is not trusted and tested format");
					}
				}
				else
				{
					if (!AcceptedFileExtension.IsAcceptedFormat(file))
					{
						throw new VzaarException(
							"Video is not of an accepted format");
					}
				}

				var signature = GetUploadSignature();

				Upload(signature, file);

				return signature.guid;
			}
			catch (VzaarException ve)
			{
				throw ve;
			}
		}


		/// <summary>
		/// Upload a video to vzaar. This is tripart request which fetches the 
		/// upload signature, uploads to S3 and then requests vzaar to process.
		/// </summary>
		/// <param name="title">The video title</param>
		/// <param name="description">The video description</param>
		/// <param name="labels"></param>
		/// <param name="profile">The size for the video to be encoded</param>
		/// <param name="file">The file to be uploaded</param>        
		/// <returns>The video number for the video</returns>
		public int UploadVideo(string title, string description, string labels, VideoProfile profile, string file)
		{

			try
			{
				if (_trustedOnly)
				{
					if (!AcceptedFileExtension.IsTrustedFormat(file))
					{
						throw new VzaarException(
							"Video is not trusted and tested format");
					}
				}
				else
				{
					if (!AcceptedFileExtension.IsAcceptedFormat(file))
					{
						throw new VzaarException(
							"Video is not of an accepted format");
					}
				}

				//S3Uploader.upload(token, secret, file);
				//return 0;

				var signature = GetUploadSignature();

				Upload(signature, file);

				var videoId = ProcessVideo(signature.guid, title, description, labels, profile);

				return videoId;
			}
			catch (VzaarException ve)
			{
				throw ve;
			}
		}


		/// <summary>
		/// Edit a video title and description.
		/// </summary>
		/// <param name="videoId">The video ID</param>
		/// <param name="title">The video title</param>
		/// <param name="description">The video description</param>
		public void EditVideo(int videoId, string title, string description)
		{
			var uri = "videos/" + videoId + ".xml";

			var xml = String.Format(XmlHelper.EDIT_VIDEO, title, description);

			var response = _transport.SendPostXmlRequest(uri, xml);

			if (response.GetStatusCode() == HttpStatusCode.OK) return;

			throw new VzaarException(GetError(response));
		}

		/// <summary>
		/// Delete a video in the user's Vzaar account.
		/// </summary>
		/// <param name="videoId">The video ID to be deleted</param>
		public void DeleteVideo(int videoId)
		{
			var uri = "videos/" + videoId + ".xml";

			var response = _transport.SendPostXmlRequest(uri, XmlHelper.DELETE_VIDEO, "DELETE");

			if (response.GetStatusCode() == HttpStatusCode.OK) return;

			throw new VzaarException(GetError(response));
			/*else
			{
				XmlHelper.ParseXml(response.GetResponse());
			}*/
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Process video with Custom profile
		/// </summary>
		/// <param name="guid"></param>
		/// <param name="title"></param>
		/// <param name="description"></param>
		/// <param name="labels"></param>
		/// <param name="width"></param>
		/// <param name="bitrate"></param>
		/// <returns></returns>
		public int ProcessVideo(string guid, string title, string description, string labels, int width, int bitrate = 256)
		{
			const string URI = "videos";

			var xml = string.Format(XmlHelper.PROCESS_VIDEO, guid, title, description, Convert.ToString(6));

			var doc = new XmlDocument();
			doc.LoadXml(xml);

			var elLabels = doc.CreateElement("labels");
			doc.SelectSingleNode("//vzaar-api/video").AppendChild(elLabels);

			var elEncoding = doc.CreateElement("encoding");
			doc.SelectSingleNode("//vzaar-api/video").AppendChild(elEncoding);

			var elWidth = doc.CreateElement("width");
			elWidth.InnerText = width.ToString(CultureInfo.InvariantCulture);
			elEncoding.AppendChild(elWidth);

			var elBitrate = doc.CreateElement("bitrate");
			elBitrate.InnerText = bitrate.ToString(CultureInfo.InvariantCulture);
			doc.AppendChild(elBitrate);

			xml = doc.OuterXml;

			var response = _transport.SendPostXmlRequest(URI, xml);

			if (response.GetStatusCode() != HttpStatusCode.Created)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());

			return Convert.ToInt32((XmlHelper.GetValue(document, "video")));
		}

		/// <summary>
		/// Process the video after it has been uploaded.
		/// </summary>
		/// <param name="guid">The guid to operate on</param>
		/// <param name="title">The title for the video</param>
		/// <param name="description">The description for the video</param>
		/// <param name="profile">the size for the video to be encoded in. If not 
		/// specified, this will use the vzaar default or the user default (if set)</param>
		/// <returns>The created video ID</returns>
		public int ProcessVideo(string guid, string title, string description, string labels, VideoProfile profile = VideoProfile.Original)
		{
			const string uri = "videos";

			var xml = string.Format(XmlHelper.PROCESS_VIDEO, guid, title, description, Convert.ToString((int)profile));

			var doc = new XmlDocument();
			doc.LoadXml(xml);

			var elLabels = doc.CreateElement("labels");
			elLabels.InnerText = labels;
			doc.SelectSingleNode("//vzaar-api/video").AppendChild(elLabels);

			xml = doc.OuterXml;


			var response = _transport.SendPostXmlRequest(uri, xml);

			if (response.GetStatusCode() != HttpStatusCode.Created)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());

			return Convert.ToInt32((XmlHelper.GetValue(document, "video")));
		}


		/// <summary>
		/// Get a global uid for vzaar and signature to upload to S3.
		/// </summary>
		/// <returns>The upload signature details.</returns>
		public UploadSignature GetUploadSignature()
		{
			const string uri = "videos/signature";

			var response = _transport.SendGetRequest(uri, null);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return new UploadSignature(
					XmlHelper.GetValue(document, "guid"),
					XmlHelper.GetValue(document, "key"),
					XmlHelper.GetBoolean(document, "https"),
					XmlHelper.GetValue(document, "acl"),
					XmlHelper.GetValue(document, "bucket"),
					XmlHelper.GetValue(document, "policy"),
					XmlHelper.GetValue(document, "expirationdate"),
					XmlHelper.GetValue(document, "accesskeyid"),
					XmlHelper.GetValue(document, "signature"));
		}

		public string GetUploadSignatureAsXml()
		{
			const string uri = "videos/signature";

			var response = _transport.SendGetRequest(uri, null);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var doc = new XmlDocument();
			doc.Load(response.GetResponse());
			return doc.OuterXml;
		}

		public UploadSignature GetUploadSignature(string redirectUrl)
		{
			var uri = "videos/signature";

			var parameters = new Dictionary<string, object> { { "success_action_redirect", redirectUrl } };

			var response = _transport.SendGetRequest(uri, parameters);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return new UploadSignature(
					XmlHelper.GetValue(document, "guid"),
					XmlHelper.GetValue(document, "key"),
					XmlHelper.GetBoolean(document, "https"),
					XmlHelper.GetValue(document, "acl"),
					XmlHelper.GetValue(document, "bucket"),
					XmlHelper.GetValue(document, "policy"),
					XmlHelper.GetValue(document, "expirationdate"),
					XmlHelper.GetValue(document, "accesskeyid"),
					XmlHelper.GetValue(document, "signature"));
		}

		public UploadSignature GetUploadSignature(bool flashRequest)
		{
			const string uri = "videos/signature";

			var parameters = new Dictionary<string, object> { { "flash_request", flashRequest ? "true" : "false" } };

			var response = _transport.SendGetRequest(uri, parameters);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return new UploadSignature(
					XmlHelper.GetValue(document, "guid"),
					XmlHelper.GetValue(document, "key"),
					XmlHelper.GetBoolean(document, "https"),
					XmlHelper.GetValue(document, "acl"),
					XmlHelper.GetValue(document, "bucket"),
					XmlHelper.GetValue(document, "policy"),
					XmlHelper.GetValue(document, "expirationdate"),
					XmlHelper.GetValue(document, "accesskeyid"),
					XmlHelper.GetValue(document, "signature"));
		}

		public string GetUploadSignatureAsXml(string redirectUrl)
		{
			var uri = "videos/signature";

			var parameters = new Dictionary<string, object> { { "success_action_redirect", redirectUrl } };

			var response = _transport.SendGetRequest(uri, parameters);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var doc = new XmlDocument();
			doc.Load(response.GetResponse());
			return doc.OuterXml;
		}

		public string GetUploadSignatureAsXml(string redirectUrl, bool flashRequest)
		{
			var uri = "videos/signature";

			var parameters = new Dictionary<string, object>
                                 {
                                     {"flash_request", flashRequest ? "true" : "false"},
                                     {"success_action_redirect", redirectUrl}
                                 };

			var response = _transport.SendGetRequest(uri, parameters);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var doc = new XmlDocument();
			doc.Load(response.GetResponse());
			return doc.OuterXml;
		}

		public string GetUploadSignatureAsXml(bool flashRequest)
		{
			var uri = "videos/signature";

			var parameters = new Dictionary<string, object> { { "flash_request", flashRequest ? "true" : "false" } };

			var response = _transport.SendGetRequest(uri, parameters);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var doc = new XmlDocument();
			doc.Load(response.GetResponse());
			return doc.OuterXml;
		}

		public UploadSignature GetUploadSignature(string redirectUrl, bool flashRequest)
		{
			const string uri = "videos/signature";

			var parameters = new Dictionary<string, object>
			                 	{
			                 		{ "flash_request", flashRequest ? "true" : "false" }, 
									{ "success_action_redirect", redirectUrl }
			                 	};

			var response = _transport.SendGetRequest(uri, parameters);

			if (response.GetStatusCode() != HttpStatusCode.OK)
			{
				throw new VzaarException(GetError(response));
			}

			var document = XmlHelper.ParseXml(response.GetResponse());
			return new UploadSignature(
					XmlHelper.GetValue(document, "guid"),
					XmlHelper.GetValue(document, "key"),
					XmlHelper.GetBoolean(document, "https"),
					XmlHelper.GetValue(document, "acl"),
					XmlHelper.GetValue(document, "bucket"),
					XmlHelper.GetValue(document, "policy"),
					XmlHelper.GetValue(document, "expirationdate"),
					XmlHelper.GetValue(document, "accesskeyid"),
					XmlHelper.GetValue(document, "signature"));
		}

		private static FormWebResponse Upload(UploadSignature signature, string file)
		{
			var _url = "https://" + signature.GetBucket() + ".s3.amazonaws.com/";
			// Setup the parameters that AWS requires for the upload form
			//Dictionary<string, object> parameters = new Dictionary<string, object>();
			var parameters = new NameValueCollection
                                 {
                                     {"key", signature.GetKey()},
                                     {"AWSAccessKeyId", signature.GetAccessKeyId()},
                                     {"acl", signature.GetAcl()},
                                     {"policy", signature.GetPolicy()},
                                     {"signature", signature.GetSignature()},
                                     {"success_action_status", "201"}
                                 };
			var resp = Form.HttpUploadFile(_url, file, "file", "application/octet-stream", parameters);
			return resp;
		}

		/// <summary>
		/// Extract the error string from the response. It will first check for 
		/// an error tag if there is an xml reponse, otherwise it will return
		/// the HTTP error status line. 
		/// </summary>
		/// <param name="response"></param>
		/// <returns></returns>
		private static string GetError(VzaarTransportResponse response)
		{
			try
			{
				var document = XmlHelper.ParseXml(response.GetResponse());
				var error = XmlHelper.GetValue(document, "error");

				if (error != null)
				{
					return error;
				}

				error = XmlHelper.GetValue(document, "Message");
				if (error != null)
				{
					return error;
				}
			}
			catch (VzaarException)
			{
			}

			Debug.WriteLine(response.GetStatusLine());

			return response.GetStatusLine();
		}

		#endregion
	}
}
